package com.qd.panda.mq.charge;

import cn.hutool.core.math.Money;
import com.cdqidi.exception.ApiException;
import com.cdqidi.util.DateUtil;
import com.cdqidi.util.RedisTemplateUtil;
import com.cdqidi.util.RedissonLockTemplate;
import com.qd.common.panda.domain.entity.stroke.UserStrokeDTO;
import com.qd.common.panda.domain.entity.stroke.UserStrokeOrderDTO;
import com.qd.common.panda.event.EtcPayEvent;
import com.qd.panda.dc.DcService;
import com.qd.panda.dc.dto.PayInfoDTO;
import com.qd.panda.service.stroke.DisplayUtil;
import com.qd.panda.service.stroke.UserStrokeService;
import com.qd.panda.service.stroke.order.UserStrokeOrderService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

import static java.util.stream.Collectors.toList;

/**
 * ETC支付
 *
 * @author sjk
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class EtcPayService {
    private static final String PAY_PROCESS = "panda_park_pay_sid_{0}";
    private final RedisTemplateUtil<String> redisTemplateUtil;
    private final UserStrokeOrderService userStrokeOrderService;
    private final UserStrokeService userStrokeService;
    private final DcService dcService;
    private final TransactionTemplate transactionTemplate;

    private final ChargeConsumerService chargeConsumerService;

    public static String getPayProcessKey(String sid) {
        return RedisTemplateUtil.getRedisCacheKey(EtcPayService.PAY_PROCESS, sid);
    }

    /**
     * etc支付充电费和停车费
     * 自己控制事务
     *
     * @param etcPayEvent 行程ID
     */
    @EventListener(classes = EtcPayEvent.class)
    public void etcChargeParkPay(EtcPayEvent etcPayEvent) {
        String sid = (String) etcPayEvent.getSource();
        final String key = EtcPayService.getPayProcessKey(sid);
        boolean b = RedissonLockTemplate.execute(sid, () -> {
            if (Boolean.TRUE.equals(redisTemplateUtil.getStringRedisTemplate().hasKey(key))) {
                log.error("当前行程正在支付,ETC支付取消,sid: {}", sid);
                return null;
            }
            redisTemplateUtil.set(key, "a");
            try {
                transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
                return transactionTemplate.execute(status -> {
                    try {
                        etcChargeParkEtcPay(sid);
                        return true;
                    } catch (Exception e) {
                        log.error("etc支付充电费和停车费失败", e);
                        status.setRollbackOnly();
                        return false;
                    }
                });
            } finally {
                redisTemplateUtil.deleteByKey(key);
            }
        }, -1);
        if(log.isDebugEnabled()) {
            log.debug("事件,etc支付: {},是否成功: {}", sid, b);
        }
    }


    @SneakyThrows
    private void etcChargeParkEtcPay(String sid) {
        final UserStrokeDTO userStrokeDTO = userStrokeService.getByIdDto(sid);
        if (null == userStrokeDTO) {
            log.error("etc支付,查询行程为空,sid: {}", sid);
            return;
        }
        final List<UserStrokeOrderDTO> payList = new ArrayList<>();
        final List<UserStrokeOrderDTO> chargeList = userStrokeOrderService.getChargeListBySid(sid);
        log.info("etcChargeParkEtcPay,充电订单是否为空: {}", chargeList.isEmpty());
        if (!chargeList.isEmpty()) {
            final List<UserStrokeOrderDTO> noPayList = chargeList.stream().filter(u -> u.getPayStatus().equals(DisplayUtil.PAY_STATUS_0) || u.getPayStatus().equals(DisplayUtil.PAY_STATUS_2) || u.getPayStatus().equals(DisplayUtil.PAY_STATUS_4)).
                    filter(u -> u.getOrderCharge().getState().equals(DisplayUtil.CHARGE_STAT_4)).collect(toList());
            log.info("etcChargeParkEtcPay,没有支付的充电订单是否为空: {}", noPayList.isEmpty());
            if (!noPayList.isEmpty()) {
                payList.addAll(noPayList);
            }
        }
        log.info("需要支付的充电订单数量： {}", payList.size());
        final CompletableFuture<PayInfoDTO> parkCompletableFuture = chargeConsumerService.getParkOrder(userStrokeDTO.getParkId(), userStrokeDTO.getPlateNumber(), userStrokeDTO.getSid());
        Optional.ofNullable(parkCompletableFuture.get()).flatMap(payInfoDTO -> Optional.ofNullable(chargeConsumerService.createUserStrokeOrderPark(payInfoDTO, userStrokeDTO))).ifPresent(payList::add);
        log.info("需要支付的充电和停车订单数量： {}", payList.size());
        doProcessChargeParkEtcPay(payList);
        update(userStrokeDTO, payList);

        userStrokeDTO.freeData();
        payList.clear();
    }

    /**
     * 如果支付成功了，那么更新订单和行程
     *
     * @param userStrokeDTO 行程
     * @param payList       订单
     * @return 是否支付成功
     */
    public boolean update(UserStrokeDTO userStrokeDTO, List<UserStrokeOrderDTO> payList) {
        Money payAmount = new Money();
        //判断是否支付成功，支付成功的话更新订单和行程信息
        boolean isPaySuccess = false;
        LocalDateTime endTime = null;
        for (UserStrokeOrderDTO userStrokeOrderDTO : payList) {
            payAmount = payAmount.add(new Money(userStrokeOrderDTO.getPayAmount()));
            userStrokeOrderService.saveOrUpdate(userStrokeOrderDTO);
            if (userStrokeOrderDTO.getPayStatus().equals(DisplayUtil.PAY_STATUS_1)) {
                isPaySuccess = true;
            }
            if (null == endTime) {
                endTime = userStrokeOrderDTO.getEndTime();
            } else {
                if (endTime.isBefore(userStrokeOrderDTO.getEndTime())) {
                    endTime = userStrokeOrderDTO.getEndTime();
                }
            }
        }
        log.info("是否支付成功,行程ID: {},支付状态: {}", userStrokeDTO.getSid(), isPaySuccess);
        if (isPaySuccess) {
            //支付成功了就说明没有欠费信息了，返回0就行
            userStrokeDTO.setPayAmount(payAmount.getAmount());
            userStrokeDTO.setAmount(userStrokeDTO.getPayAmount());
            userStrokeDTO.setOnLinePayAmount(userStrokeDTO.getPayAmount());
            userStrokeDTO.setBillEndTime(endTime);
            userStrokeDTO.setProcessStatus(DisplayUtil.PROCESS_STATUS_4);
            userStrokeService.update(userStrokeDTO);
        }
        return isPaySuccess;
    }

    /**
     * etc支付充电费
     * 自己控制事务
     *
     * @param sid  行程ID
     * @param list 行程ID
     */
    public void etcChargeParkPay(String sid, List<UserStrokeOrderDTO> list) {
        final String key = getPayProcessKey(sid);
        boolean b = RedissonLockTemplate.execute(sid, () -> {
            if (Boolean.TRUE.equals(redisTemplateUtil.getStringRedisTemplate().hasKey(key))) {
                log.error("当前行程正在支付,ETC支付取消,sid: {}", sid);
                return null;
            }
            redisTemplateUtil.set(key, "a");
            try {
                transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
                return transactionTemplate.execute(status -> {
                    try {
                        doProcessChargeParkEtcPay(list);
                        return true;
                    } catch (Exception e) {
                        log.error("etc支付充电费和停车费失败", e);
                        status.setRollbackOnly();
                        return false;
                    }
                });
            } finally {
                redisTemplateUtil.deleteByKey(key);
            }
        }, -1);
        if(log.isDebugEnabled()) {
            log.debug("事件,etc支付: {},是否成功: {}", sid, b);
        }
    }


    /**
     * 出场的时候通过ETC支付停车费和充电费，如果支付失败，不保存支付状态
     *
     * @param list 订单信息
     */
    private void doProcessChargeParkEtcPay(List<UserStrokeOrderDTO> list) {
        LocalDateTime now = LocalDateTime.now();
        final List<UserStrokeOrderDTO> noPayList = list.stream().filter(userStrokeOrderDTO -> userStrokeOrderDTO.getPayStatus().equals(DisplayUtil.PAY_STATUS_0)
                || userStrokeOrderDTO.getPayStatus().equals(DisplayUtil.PAY_STATUS_2) || userStrokeOrderDTO.getPayStatus().equals(DisplayUtil.PAY_STATUS_4)).collect(toList());
        final List<UserStrokeOrderDTO> parkList = noPayList.stream().filter(userStrokeOrderDTO -> userStrokeOrderDTO.getTripType().equals(DisplayUtil.TRIP_TYPE_0)).collect(toList());
        final List<UserStrokeOrderDTO> chargeList = noPayList.stream().filter(userStrokeOrderDTO -> userStrokeOrderDTO.getTripType().equals(DisplayUtil.TRIP_TYPE_1)).collect(toList());
        //正常情况下，充电的在收到ETC推送的时候就已经支付了，此时应该只有一条停车的。
        if ((!parkList.isEmpty() && parkList.size() > 1) || (!chargeList.isEmpty() && chargeList.size() > 1)) {
            log.info("停车订单数量: {},充电订单数量: {}", parkList.size(), chargeList.size());
            //停车和充电如果都有超过一个订单，那么就循环调用
            for (UserStrokeOrderDTO userStrokeOrderDTO : noPayList) {
                String payNo = null;
                String parkTime = null;
                String laneCode = null;
                String startChargeSeq = null;
                if (null == userStrokeOrderDTO.getEndTime()) {
                    userStrokeOrderDTO.setEndTime(now);
                }
                if (userStrokeOrderDTO.getTripType() == DisplayUtil.TRIP_TYPE_0) {
                    payNo = userStrokeOrderDTO.getThirdPartyOrderId();
                    laneCode = userStrokeOrderDTO.getExtendsFiles();
                    if (null != userStrokeOrderDTO.getStartTime()) {
                        parkTime = Duration.between(userStrokeOrderDTO.getStartTime(), userStrokeOrderDTO.getEndTime()).toMinutes() + "";

                    }
                } else if (userStrokeOrderDTO.getTripType() == DisplayUtil.TRIP_TYPE_1) {
                    startChargeSeq = userStrokeOrderDTO.getThirdPartyOrderId();
                } else {
                    log.error("未知的行程类型,trip_type: {}", userStrokeOrderDTO.getTripType());
                    throw new ApiException("未知的行程类型，不处理");
                }
                if (userStrokeOrderDTO.getPayAmount().signum() == BigDecimal.ZERO.signum()) {
                    //表示金额为0，不需要支付，直接就是支付成功
                    userStrokeOrderDTO.setPayStatus(DisplayUtil.PAY_STATUS_1);
                } else {
                    try {
                        final boolean pay = dcService.pay(userStrokeOrderDTO.getParkId(), userStrokeOrderDTO.getPlateNumber(), userStrokeOrderDTO.getPayAmount(), payNo, startChargeSeq, parkTime, laneCode, DateUtil.localDateTimeToStr(userStrokeOrderDTO.getStartTime()), DateUtil.localDateTimeToStr(userStrokeOrderDTO.getEndTime()));
                        if (pay) {
                            userStrokeOrderDTO.setPayStatus(DisplayUtil.PAY_STATUS_1);
                        } else {
                            userStrokeOrderDTO.setPayStatus(DisplayUtil.PAY_STATUS_2);
                        }
                        log.info("循环调用ETC支付接口,orderId: {},支付状态: {}", userStrokeOrderDTO.getOrderId(), pay);
                    } catch (Exception e) {
                        userStrokeOrderDTO.setPayStatus(DisplayUtil.PAY_STATUS_2);
                        log.error("etc支付失败,orderId: {}", userStrokeOrderDTO.getOrderId(), e);
                    }
                }
            }
            return;
        }

        UserStrokeOrderDTO park = null;
        UserStrokeOrderDTO charge = null;
        String payNo = null;
        String parkTime = null;
        String laneCode = null;
        String startChargeSeq = null;
        String startTime = null;
        String endTime = null;
        String parkId = null;
        String plateNumber = null;
        Money amount = new Money();
        String chargeOrderId = null;
        String parkOrderId = null;
        if (parkList.size() == 1) {
            park = parkList.get(0);
            parkId = park.getParkId();
            plateNumber = park.getPlateNumber();
            payNo = park.getThirdPartyOrderId();
            laneCode = park.getExtendsFiles();
            if (null != park.getStartTime()) {
                parkTime = Duration.between(park.getStartTime(), park.getEndTime()).toMinutes() + "";
            }
            amount = amount.add(new Money(park.getPayAmount()));
            startTime = DateUtil.localDateTimeToStr(park.getStartTime());
            endTime = DateUtil.localDateTimeToStr(park.getEndTime());
            parkOrderId = park.getOrderId();
        }
        if (chargeList.size() == 1) {
            charge = chargeList.get(0);
            startChargeSeq = charge.getThirdPartyOrderId();
            amount = amount.add(new Money(charge.getPayAmount()));
            chargeOrderId = charge.getOrderId();
        }
        if (amount.getAmount().signum() == BigDecimal.ZERO.signum()) {
            //表示金额为0，不需要支付，直接就是支付成功
            if (null != park) {
                park.setPayStatus(DisplayUtil.PAY_STATUS_1);
            }
            if (null != charge) {
                charge.setPayStatus(DisplayUtil.PAY_STATUS_1);
            }
        } else {
            try {
                final boolean pay = dcService.pay(parkId, plateNumber, amount.getAmount(), payNo, startChargeSeq, parkTime, laneCode, startTime, endTime);
                if (pay) {
                    if (null != park) {
                        park.setPayStatus(DisplayUtil.PAY_STATUS_1);
                    }
                    if (null != charge) {
                        charge.setPayStatus(DisplayUtil.PAY_STATUS_1);
                    }
                } else {
                    if (null != park) {
                        park.setPayStatus(DisplayUtil.PAY_STATUS_2);
                    }
                    if (null != charge) {
                        charge.setPayStatus(DisplayUtil.PAY_STATUS_2);
                    }
                }
                log.info("合并调用ETC支付接口,停车的orderId: {}，充电的orderId: {},支付状态: {}", parkOrderId, chargeOrderId, pay);
            } catch (Exception e) {
                log.error("etc支付失败,停车的orderId: {}，充电的orderId: {}", parkOrderId, chargeOrderId, e);
                if (null != park) {
                    park.setPayStatus(DisplayUtil.PAY_STATUS_2);
                }
                if (null != charge) {
                    charge.setPayStatus(DisplayUtil.PAY_STATUS_2);
                }
            }
        }

    }
}